package com.lansosdk.videoeditor;

import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;

import com.lansosdk.util.LanSongFileUtil;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import static com.lansosdk.util.LanSongFileUtil.fileExist;


/**
 * 最简单的调用方法:
 * VideoEditor veditor=new VideoEditor();
 * veditor.executeXXXXX();  //阻塞执行, 要放到AsyncTask或Thread中执行;
 * <p>
 * 杭州蓝松科技有限公司
 * www.lansongtech.com
 */
public class VideoEditor {


    private static final String TAG = "LanSoJni";

    public static final String version = "20180728_version";
    /**
     * 使用软件编码的列表;
     */
    public static String[] useSoftEncoderlist = {
            "EML-AL00",
            "EML-AL01",
            "LON-AL00",
            "MHA-AL00",
            "STF-AL00",
            "CLT-AL00",
            "ALP-AL00"
    };

    /**
     * 解析参数失败 返回1
     无输出文件 2；
     输入文件为空：3
     sdk未授权 -1；
     解码器错误：69
     收到线程的中断信号：255
     如硬件编码器错误，则返回：26625---26630
     */
    /**
     * 是否强制使用硬件编码器;
     * 默认先硬件编码,如果无法完成则切换为软编码
     */
    public static boolean isForceHWEncoder = false;
    /**
     * 是否强制使用软件编码器
     * 默认先硬件编码,如果无法完成则切换为软编码
     */
    public static boolean isForceSoftWareEncoder = false;

    /**
     * 给当前方法指定码率.
     * 此静态变量, 在execute执行后, 默认恢复为0;
     */
    private int encodeBitRate = 0;
    public static final int VIDEO_EDITOR_EXECUTE_SUCCESS1 = 0;
    public static final int VIDEO_EDITOR_EXECUTE_SUCCESS2 = 1;
    public static final int VIDEO_EDITOR_EXECUTE_FAILED = -101;  //文件不存在。


    private final int VIDEOEDITOR_HANDLER_PROGRESS = 203;
    private final int VIDEOEDITOR_HANDLER_COMPLETED = 204;
    private final int VIDEOEDITOR_HANDLER_ENCODERCHANGE = 205;


    private static LanSongLogCollector lanSongLogCollector = null;


    public void setEncodeBitRate(int bitRate) {
        encodeBitRate = bitRate;
    }

    /**
     * 使能在ffmpeg执行的时候, 收集错误信息;
     *
     * @param ctx
     */
    public static void logEnable(Context ctx) {

        if (ctx != null) {
            lanSongLogCollector = new LanSongLogCollector(ctx);
        } else {
            if (lanSongLogCollector != null && lanSongLogCollector.isRunning()) {
                lanSongLogCollector.stop();
                lanSongLogCollector = null;
            }
        }
    }

    /**
     * 当执行失败后,返回错误信息;
     *
     * @return
     */
    public static String getErrorLog() {
        if (lanSongLogCollector != null && lanSongLogCollector.isRunning()) {
            return lanSongLogCollector.stop();
        } else {
            return null;
        }
    }

    /**
     * 构造方法.
     * 如果您想扩展ffmpeg的命令, 可以继承这个类,然后在其中像我们的各种executeXXX的举例一样来拼接ffmpeg的命令;不要直接修改我们的这个文件, 以方便以后的sdk更新升级.
     */

    public VideoEditor() {
        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
            Log.w(TAG, "cannot get Looper handler. may be cannot receive video editor progress!!");
        }
    }

    public onVideoEditorEncodeChangedListener mEncoderChangeListener = null;

    public void setOnEncodeChangedListener(onVideoEditorEncodeChangedListener listener) {
        mEncoderChangeListener = listener;
    }

    private void doEncoderChangedListener(boolean isSoft) {
        if (mEncoderChangeListener != null)
            mEncoderChangeListener.onChanged(this, isSoft);
    }


    public onVideoEditorProgressListener mProgressListener = null;

    public void setOnProgessListener(onVideoEditorProgressListener listener) {
        mProgressListener = listener;
    }

    private void doOnProgressListener(int timeMS) {
        if (mProgressListener != null)
            mProgressListener.onProgress(this, timeMS);
    }

    private EventHandler mEventHandler;



    private class EventHandler extends Handler {
        private final WeakReference<VideoEditor> mWeakExtract;

        public EventHandler(VideoEditor mp, Looper looper) {
            super(looper);
            mWeakExtract = new WeakReference<VideoEditor>(mp);
        }

        @Override
        public void handleMessage(Message msg) {
            VideoEditor videoextract = mWeakExtract.get();
            if (videoextract == null) {
                Log.e(TAG, "VideoEditor went away with unhandled events");
                return;
            }
            switch (msg.what) {
                case VIDEOEDITOR_HANDLER_PROGRESS:
                    videoextract.doOnProgressListener(msg.arg1);
                    break;
                case VIDEOEDITOR_HANDLER_ENCODERCHANGE:
                    videoextract.doEncoderChangedListener(true);  //暂停只要改变,就变成软编码;
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 异步线程执行的代码.
     */
    public int executeVideoEditor(String[] array) {
        return execute(array);
    }

    @SuppressWarnings("unused") /* Used from JNI */
    private void postEventFromNative(int what, int arg1, int arg2) {
        Log.i(TAG, "postEvent from native  is:" + what);

        if (mEventHandler != null) {
            Message msg = mEventHandler.obtainMessage(VIDEOEDITOR_HANDLER_PROGRESS);
            msg.arg1 = what;
            mEventHandler.sendMessage(msg);
        }
    }

    protected void sendEncoderEnchange() {
        if (mEventHandler != null) {
            Message msg = mEventHandler.obtainMessage(VIDEOEDITOR_HANDLER_ENCODERCHANGE);
            mEventHandler.sendMessage(msg);
        }
    }

    /**
     * 获取当前版本号
     *
     * @return
     */
    public static native String getSDKVersion();

    /**
     * 执行成功,返回0, 失败返回错误码.
     * <p>
     * 解析参数失败 返回1
     * sdk未授权 -1；
     * 解码器错误：69
     * 收到线程的中断信号：255
     * 如硬件编码器错误，则返回：26625---26630
     *
     * @param cmdArray ffmpeg命令的字符串数组, 可参考此文件中的各种方法举例来编写.
     * @return 执行成功, 返回0, 失败返回错误码.
     */
    private native int execute(Object cmdArray);

    /**
     * 新增 在执行过程中取消的方法.
     * 如果在执行中调用了这个方法, 则会直接终止当前的操作.
     * 此方法仅仅是在ffmpeg线程中设置一个标志位,当前这一帧处理完毕后, 会检测到这个标志位,从而退出.
     * 因为execute是阻塞执行, 你可以判断execute有没有执行完,来判断是否完成.
     */
    public native void cancel();


    /**
     * 两个pcm格式的音频数据,(裸数据)混合.
     *
     * @param srcPach1    pcm格式的主音频
     * @param samplerate  主音频采样率
     * @param channel     主音频通道数
     * @param srcPach2    pcm格式的次音频
     * @param samplerate2 次音频采样率
     * @param channel2    次音频通道数
     * @param value1      主音频的音量
     * @param value2      次音频的音量
     * @return 输出文件.输出也是pcm格式的音频文件.
     */
    public String executePcmMix(String srcPach1, int samplerate, int channel, String srcPach2, int samplerate2, int
            channel2, float value1, float value2) {
        List<String> cmdList = new ArrayList<String>();

        String filter = String.format(Locale.getDefault(), "[0:a]volume=volume=%f[a1]; [1:a]volume=volume=%f[a2]; " +
                "[a1][a2]amix=inputs=2:duration=first:dropout_transition=2", value1, value2);

        String dstPath = LanSongFileUtil.createFileInBox("pcm");

        cmdList.add("-f");
        cmdList.add("s16le");
        cmdList.add("-ar");
        cmdList.add(String.valueOf(samplerate));
        cmdList.add("-ac");
        cmdList.add(String.valueOf(channel));
        cmdList.add("-i");
        cmdList.add(srcPach1);

        cmdList.add("-f");
        ;
        cmdList.add("s16le");
        cmdList.add("-ar");
        cmdList.add(String.valueOf(samplerate2));
        cmdList.add("-ac");
        cmdList.add(String.valueOf(channel2));
        cmdList.add("-i");
        cmdList.add(srcPach2);

        cmdList.add("-y");
        cmdList.add("-filter_complex");
        cmdList.add(filter);
        cmdList.add("-f");
        cmdList.add("s16le");
        cmdList.add("-acodec");
        cmdList.add("pcm_s16le");
        cmdList.add(dstPath);


        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }
    }



    /**
     * 把 pcm和视频文件合并在一起, pcm数据会编码成aac格式.
     * 注意:需要原视频文件里没有音频部分, 如果有, 则需要先用
     *
     * @param srcPcm     原pcm音频文件,
     * @param samplerate pcm的采样率
     * @param channel    pcm的通道数
     * @param srcVideo   原视频文件, 没有音频部分
     * @return 输出的视频文件路径, 需后缀是mp4格式.
     */
    public String executePcmComposeVideo(Context context,String srcPcm, int samplerate, int channel, String srcVideo) {
        List<String> cmdList = new ArrayList<String>();

        String dstPath = LanSongFileUtil.createMp4FileInBox(context);
        cmdList.add("-f");
        cmdList.add("s16le");
        cmdList.add("-ar");
        cmdList.add(String.valueOf(samplerate));
        cmdList.add("-ac");
        cmdList.add(String.valueOf(channel));
        cmdList.add("-i");
        cmdList.add(srcPcm);

        cmdList.add("-i");
        cmdList.add(srcVideo);

        cmdList.add("-acodec");
        cmdList.add("libfaac");
        cmdList.add("-b:a");
        cmdList.add("64000");
        cmdList.add("-y");

        cmdList.add("-vcodec");
        cmdList.add("copy");

        cmdList.add(dstPath);


        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }
    }

    /**
     * 两个音频文件延迟混合, 即把第二个音频延迟多长时间后, 与第一个音频混合.
     * 混合后的编码为aac格式的音频文件.
     * 注意,如果两个音频的时长不同, 以第一个音频的音频为准. 如需修改可联系我们或查询ffmpeg命令即可.
     *
     * @param audioPath1
     * @param audioPath2
     * @param leftDelayMS  第二个音频的左声道 相对 于第一个音频的延迟时间
     * @param rightDelayMS 第二个音频的右声道 相对 于第一个音频的延迟时间
     * @return
     */
    public String executeAudioDelayMix(Context ctx,String audioPath1, String audioPath2, int leftDelayMS, int rightDelayMS) {
        List<String> cmdList = new ArrayList<String>();
        String overlayXY = String.format(Locale.getDefault(), "[1:a]adelay=%d|%d[delaya1]; " +
                "[0:a][delaya1]amix=inputs=2:duration=first:dropout_transition=2", leftDelayMS, rightDelayMS);


        String dstPath = LanSongFileUtil.createM4AFileInBox(ctx);
        cmdList.add("-i");
        cmdList.add(audioPath1);

        cmdList.add("-i");
        cmdList.add(audioPath2);

        cmdList.add("-filter_complex");
        cmdList.add(overlayXY);

        cmdList.add("-acodec");
        cmdList.add("libfaac");

        cmdList.add("-y");
        cmdList.add(dstPath);
        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }
    }

    /**
     * 两个音频文件混合.
     * 混合后的文件压缩格式是mp4格式
     *
     * @param audioPath1 主音频的完整路径
     * @param audioPath2 次音频的完整路径
     * @param value1     主音频的音量, 浮点类型, 大于1.0为放大音量, 小于1.0是减低音量.比如设置0.5则降低一倍.
     * @param value2     次音频的音量, 浮点类型.
     * @return 输出保存的完整路径. mp4格式.
     */
    public String executeAudioVolumeMix(Context ctx,String audioPath1, String audioPath2, float value1, float value2) {
        List<String> cmdList = new ArrayList<String>();

        String filter = String.format(Locale.getDefault(), "[0:a]volume=volume=%f[a1]; [1:a]volume=volume=%f[a2]; " +
                "[a1][a2]amix=inputs=2:duration=first:dropout_transition=2", value1, value2);

        String dstPath = LanSongFileUtil.createMp4FileInBox(ctx);
        cmdList.add("-i");
        cmdList.add(audioPath1);

        cmdList.add("-i");
        cmdList.add(audioPath2);

        cmdList.add("-filter_complex");
        cmdList.add(filter);

        cmdList.add("-acodec");
        cmdList.add("libfaac");

        cmdList.add("-y");
        cmdList.add(dstPath);
        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }
    }


    public String executeMP3ToMP4(Context ctx,String mainAudio) {
        List<String> cmdList = new ArrayList<>();

        String dstPath = LanSongFileUtil.createMp4FileInBox(ctx);

        cmdList.add("-i");
        cmdList.add(mainAudio);

        cmdList.add("-map");
        cmdList.add("0:a");



        cmdList.add("-y");
        cmdList.add(dstPath);

        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }

    }

    public String contactArm(Context ctx,List<String> list){
        String file = "concat:";
        for (int i = 0; i < list.size(); i++) {
            file += list.get(i);
            if (i != list.size() -1)
                file += "|";
        }
        Log.e("test:","finle:"+file);
        List<String> cmdList = new ArrayList<>();

        //ffmpeg64.exe -i "concat:123.mp3|124.mp3" -acodec copy output.mp3
//        解释：-i代表输入参数
//        contact:123.mp3|124.mp3代表着需要连接到一起的音频文件
//                -acodec copy output.mp3 重新编码并复制到新文件中

        String dstPath = LanSongFileUtil.createMP3FileInBox(ctx);

        cmdList.add("-i");
        cmdList.add(file);
        cmdList.add("-acodec");
        cmdList.add("copy");
        cmdList.add(dstPath);
        cmdList.add("-y");
        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }
    }


    public String executeAMRToMP3(Context ctx,String mainAudio) {
        List<String> cmdList = new ArrayList<>();

        //MP3转换AMR： ffmpeg -i 1.mp3 -ac 1 -ar 8000 1.amr

//        AMR转换MP3： ffmpeg -i 1.amr 1.mp3
        //ffmpeg -i source.amr -acodec libmp3lame -ab 32k -ar 22050 -ac 2 dist.mp3

        String dstPath = LanSongFileUtil.createMP3FileInBox(ctx);

        cmdList.add("-i");
        cmdList.add(mainAudio);
//        cmdList.add("-acodec");
//        cmdList.add("libmp3lame");
//        cmdList.add("-ab");
//        cmdList.add("32k");
//        cmdList.add("-ar");
//        cmdList.add("22050");
//        cmdList.add("-ac");
//        cmdList.add("2");
        cmdList.add(dstPath);
        cmdList.add("-y");
        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }

    }

    /**
     * M3U8转mp4格式
     * @param ctx
     * @param m3u8Path
     * @param mp4Path
     * @return
     */
    public String executeM3U8ToMP4(Context ctx, String m3u8Path, String mp4Path) {
        Log.e(TAG,"executeM3U8ToMP4_m3u8Path:"+m3u8Path);
        Log.e(TAG,"executeM3U8ToMP4__mp4Path："+mp4Path);
        List<String> cmdList = new ArrayList<>();
        String dstPath = LanSongFileUtil.createMp4FileInBox(ctx);
        Log.e(TAG,"executeM3U8ToMP4_createMp4FileInBox:"+dstPath);
        cmdList.add("-i");
        cmdList.add(m3u8Path);
        cmdList.add("-vcodec");
        cmdList.add("copy");
        cmdList.add("-acodec");
        cmdList.add("copy");
        cmdList.add("-absf");
        cmdList.add("aac_adtstoasc");
//        cmdList.add(mp4Path);
        cmdList.add(mp4Path);
        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return mp4Path;
        } else {
            LanSongFileUtil.deleteFile(mp4Path);
            return null;
        }
    }

    public String executeMixAudioToMP4(Context ctx,String mainAudio, String audioStr2) {
        List<String> cmdList = new ArrayList<>();

        String dstPath = LanSongFileUtil.createMp4FileInBox(ctx);

        cmdList.add("-i");
        cmdList.add(mainAudio);

       cmdList.add("-i");
        cmdList.add(audioStr2);

        cmdList.add("-map");
        cmdList.add("0:a");

        cmdList.add("-map");
        cmdList.add("1:a");


        cmdList.add("-y");
        cmdList.add(dstPath);

        String[] command = new String[cmdList.size()];
        for (int i = 0; i < cmdList.size(); i++) {
            command[i] = (String) cmdList.get(i);
        }
        int ret = executeVideoEditor(command);
        if (ret == 0) {
            return dstPath;
        } else {
            LanSongFileUtil.deleteFile(dstPath);
            return null;
        }

    }




    /**
     * 增加编码器,并开始执行;
     *
     * @param cmdList
     * @param bitrate
     * @param dstPath
     * @param isHWEnc 是否使用硬件编码器; 如果强制了,则以强制为准;
     * @return
     */
    public int executeWithEncoder(List<String> cmdList, int bitrate, String dstPath, boolean isHWEnc) {
        List<String> cmdList2 = new ArrayList<String>();
        for (String item : cmdList) {
            cmdList2.add(item);
        }
        cmdList2.add("-vcodec");

        if (isHWEnc) {
            cmdList2.add("lansoh264_enc");
            cmdList2.add("-pix_fmt");
            cmdList2.add("yuv420p");
            cmdList2.add("-b:v");
            cmdList2.add(String.valueOf(bitrate));

        } else {
            cmdList2.add("libx264");

            cmdList2.add("-bf");
            cmdList2.add("0");

            cmdList2.add("-pix_fmt");
            cmdList2.add("yuv420p");

            cmdList2.add("-g");
            cmdList2.add("30");
            if (bitrate == 0) {
                bitrate = (int) (1.5f * 1024 * 1024);//LSTODO
            }
            cmdList2.add("-b:v");
            cmdList2.add(String.valueOf(bitrate));
        }


        cmdList2.add("-y");
        cmdList2.add(dstPath);
        String[] command = new String[cmdList2.size()];
        for (int i = 0; i < cmdList2.size(); i++) {
            command[i] = (String) cmdList2.get(i);
        }
        int ret = executeVideoEditor(command);
        return ret;
    }

    /**
     * 检测是否需要软编码;
     *
     * @return
     */
    public boolean checkSoftEncoder() {
        for (String item : useSoftEncoderlist) {
            if (item.equalsIgnoreCase(Build.MODEL)) {
                isForceSoftWareEncoder = true;
                return true;
            }
        }
        return false;
    }

    /**
     * 当数据不是16的倍数的时候,把他调整成16的倍数, 以最近的16倍数为准;
     * 举例如下:
     * 16, 17, 18, 19,20,21,22,23 ==>16;
     * 24,25,26,27,28,29,30,31,32==>32;
     * <p>
     * <p>
     * 如果是18,19这样接近16,则等于16, 等于缩小了原有的画面,
     * 如果是25,28这样接近32,则等于32,  等于稍微拉伸了原来的画面,
     * 因为最多缩小或拉伸8个像素, 还不至于画面严重变形,而又兼容编码器的要求,故可以这样做.
     *
     * @return
     */
    public static int make16Closest(int value) {

        if (value < 16) {
            return value;
        } else {
            value += 8;
            int val2 = value / 16;
            val2 *= 16;
            return val2;
        }
    }

    /**
     * 把数据变成16的倍数, 以大于等于16倍数的为准;
     * 比如:
     * 16-->返回16;
     * 17---31-->返回32;
     *
     * @param value
     * @return
     */
    public static int make16Next(int value) {
        if (value % 16 == 0) {
            return value;
        } else {
            return ((int) (value / 16.0f + 1) * 16);
        }
    }


    private static int checkCPUName() {
        String str1 = "/proc/cpuinfo";
        String str2 = "";
        try {
            FileReader fr = new FileReader(str1);
            BufferedReader localBufferedReader = new BufferedReader(fr, 8192);
            str2 = localBufferedReader.readLine();
            while (str2 != null) {
                Log.i("testCPU", "->" + str2 + "<-");
                str2 = localBufferedReader.readLine();
                if (str2.contains("SDM845")) {  //845的平台;

                }
            }
            localBufferedReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }
}
